﻿using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.IO;
using System.Linq;
using System.Runtime.Serialization;
using System.Runtime.Serialization.Formatters.Binary;
using System.Text;
using System.Threading;

using Jarvis.Sensors;
using Jarvis.Learners.DecisionTree;
using Jarvis.Rules;
using Jarvis.Utility;

namespace Jarvis.Learners
{
    public class LearnerManager
    {
        public ObservableCollection<Rule> Rules = null;

        private Dictionary<String, Learner> learners;
        private Dictionary<string, string> lastState = new Dictionary<string, string>();
        private SensorNotifier notifier;
        private Timer timer;

        public PredictionUpdatedHandler PredictionUpdate;
        public LearnersUpdatedHandler LearnersUpdate;

        public String TextForLearner(String learner)
        {
            if (learner.StartsWith("Social"))
                learner = "Social";

            try
            {
                return learners[learner].ToString();
            }
            catch (KeyNotFoundException)
            {
                return "No such learner: " + learner;
            }
        }

        public void RenameLabelForLearner(String learner, String oldLabel, String newLabel)
        {
            if (learner.StartsWith("Social"))
                learner = "Social";

            Learner l = this.learners[learner];

            foreach (Example ex in l.examples)
            {
                if (ex.label.Equals(oldLabel))
                    ex.label = newLabel;
            }

            l.Train();
            l.Save();

            this.LearnersUpdate(this, new EventArgs());
        }

        public void DeleteLabelForLearner(String learner, String label)
        {
            if (learner.StartsWith("Social"))
                learner = "Social";

            Learner l = this.learners[learner];

            List<Example> toDelete = new List<Example>();

            foreach (Example ex in l.examples)
            {
                if (ex.label.Equals(label))
                    toDelete.Add(ex);
            }

            foreach (Example ex in toDelete)
                l.examples.Remove(ex);

            l.Train();
            l.Save();

            this.LearnersUpdate(this, new EventArgs());
        }

        public void Load()
        {
            String appDataFolder = Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData) + "\\The Pennyworth Project\\Jarvis\\Learners\\";
            String appData = appDataFolder + "Rules.rules";

            if (File.Exists(appData))
            {
                try
                {
                    IFormatter formatter = new BinaryFormatter();

                    Stream s = new FileStream(appData, FileMode.Open, FileAccess.Read, FileShare.Read);
                    Rules = (ObservableCollection<Rule>)formatter.Deserialize(s);
                    s.Close();
                }
                catch (SerializationException)
                {
                    Rules = new ObservableCollection<Rule>();
                }
            }
            else
                Rules = new ObservableCollection<Rule>();
        }

        public void Save()
        {
            String appDataFolder = Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData) + "\\The Pennyworth Project\\Jarvis\\Learners\\";
            String appData = appDataFolder + "Rules.rules";

            if (!Directory.Exists(appDataFolder))
            {
                Directory.CreateDirectory(appDataFolder);

                this.Save();
            }
            else
            {
                if (Rules != null)
                {
                    IFormatter formatter = new BinaryFormatter();

                    Stream s = new FileStream(appData, FileMode.Create, FileAccess.Write, FileShare.Read);
                    formatter.Serialize(s, Rules);

                    s.Close();
                }
            }
        }

        public LearnerManager()
        {
            this.learners = new Dictionary<string, Learner>();

            List<String> l = new List<String>();
            l.Add("Activity");
            l.Add("Location");
            l.Add("Social");

            foreach (String key in l)
            {
                C45DecisionTreeLearner learner = new C45DecisionTreeLearner();
                learner.name = key;

                this.learners[key] = learner;
            }

            this.notifier = SensorNotifier.Instance;

            this.notifier.SensorsUpdate += new SensorNotifier.SensorsUpdatedHandler(this.SensorsUpdated);
            timer = null;

            this.Load();
        }

        public void SensorsUpdated(object obj, EventArgs eventArgs)
        {
            if (timer == null)
                timer = new Timer(new TimerCallback(this.FetchPrediction), null, 1000, Timeout.Infinite);
        }

        public void FetchPrediction(Object stateInfo)
        {
            if (Rules == null)
                Rules = new ObservableCollection<Rule>();

            bool paused = false;

            try
            {
                paused = (bool)Preferences.preferences.Get("Paused?");
            }
            catch (NullReferenceException)
            {
                paused = false;
            }

            if (!paused)
            {
                Dictionary<String, object> state = notifier.getState();

                foreach (String key in learners.Keys)
                {
                    String prediction = null;

                    foreach (Rule rule in Rules)
                    {
                        if (prediction == null && rule.ContextElement.Equals(key))
                        {
                            if (rule.EvaluateState(state))
                                prediction = rule.Outcome;
                        }
                    }

                    if (prediction == null)
                    {
                        Learner l = learners[key];

                        prediction = l.GetPrediction(state);

                        if (prediction == null)
                            prediction = "Unknown";
                    }

                    Boolean newContext = false;

                    if (lastState.ContainsKey(key))
                    {
                        string oldPrediction = lastState[key];

                        if (oldPrediction == null || !oldPrediction.Equals(prediction))
                            newContext = true;
                    }
                    else
                        newContext = true;

                    lastState[key] = prediction;

                    if (newContext)
                        this.PredictionUpdate(this, new EventArgs(), key, prediction);
                }
            }

            timer.Dispose();
            timer = null;
        }

        public void SetPrediction(string key, string label)
        {
            if (learners.ContainsKey(key))
            {
                Learner l = learners[key];

                Dictionary<string, object> state = notifier.getState();

                l.AddExampleForClass(state, label);

                this.LearnersUpdate(this, new EventArgs());
            }
        }

        public void UndoLastPrediction()
        {
            foreach (String key in learners.Keys)
            {
                Learner l = learners[key];
                l.RemoveLastExample();
            }

            this.LearnersUpdate(this, new EventArgs());
        }

        public delegate void PredictionUpdatedHandler(object obj, EventArgs eventArgs, string key, string value);
        public delegate void LearnersUpdatedHandler(object obj, EventArgs eventArgs);
    }
}
